local ActionBubble = 1
local ActionAnim = 2
local ActionAct = 3

local FSMWalk = 1
local FSMCreature = 2
local FSMAction = 3
local FSMWait = 4
local FSMLost = 100

local t = {waypoint=1,creatures={},timers={},fsm=FSMWalk}

function main(pPlayer, pQuestLog, args, status)
    t.pPlayer, t.pQuestLog = pPlayer, pQuestLog
    t.instance = pPlayer:GetMapInstance()
    t.cfg = LoadConfigFile(string.format(
        'scripts/Quest/Convoy/%d.lua', pQuestLog:GetQuestTypeID()))
    t4player.handler = t.pPlayer:AttachObjectHookInfo(t4player)
    if not status then
        t.pPlayer:SaveConvoyStatus(pQuestLog:GetQuestTypeID())
    end
    t.RestoreConvoyStatus(status)
end

function t.RestoreConvoyStatus(status)
    local hp, pos = 1, nil
    if status and #status > 0 then
        status = status:splitnumber(',')
        t.waypoint, hp = status[1], status[2]
        pos = table.slice(status, 3)
    else
        pos = table.slice(t.cfg.actor, 2, 5)
    end
    t.actor = t.instance:DeployCreature(t.cfg.actor[1],
        pos[1], pos[2], pos[3], pos[4], table.unpack(t.cfg.actor, 6))
    t.actor:AttachObjectHookInfo(t4actor)
    t.actor:SetS64Value(UNIT64_FIELD_HP,
        math.max(t.actor:GetS64Value(UNIT64_FIELD_HP_MAX) * hp, 1))
    table.insert(t.timers, CreateHandlerTimer(t.instance,
        function() t.Update() end, 1000))
end

function t.CleanConvoyStatus()
    for i, pCreature in ipairs(t.creatures) do
        if pCreature:is_object_alive() then
            pCreature:FastDisappear()
        end
    end
    for i, timer in ipairs(t.timers) do
        RemoveTimer(t.instance, timer)
    end
    if not t.actor:IstobeDisappear() then
        t.actor:FastDisappear()
    end
end

function t.Update()
    if t.fsm == FSMWalk then
        if t.waypoint > #t.cfg then
            t.pPlayer:GetQuestStorage():OnConvoy(pQuestLog:GetQuestTypeID(), true)
            return
        end
        if not t.tgtpos then
            t.tgtpos = vector3f(table.unpack(t.cfg[t.waypoint].waypoint))
        end
        if not t.actor:IsInCombat() then
            if t.actor:GetDistanceSq(t.tgtpos) <= 1e-2 then
                t.fsm, t.tgtpos = FSMCreature, false
            else
                if t.actor:GetDistanceSq2obj(t.pPlayer) <= 50^2 then
                    if not t.actor:IsMoving() then
                        MoveToPosition(t.actor, t.tgtpos)
                    end
                else
                    t.fsm = FSMLost
                end
            end
        end
    elseif t.fsm == FSMLost then
        if not t.actor:IsInCombat() then
            if not t.lostime or t.lostime + 5000 < systime() then
                t.islost = systime()
                t.Player:SendPlayTips(11, 3000)
            end
            if t.actor:GetDistanceSq2obj(t.pPlayer) <= 50^2 then
                t.fsm = FSMWalk
            else
                if t.actor:IsMoving() then
                    t.actor:ForceMotionless()
                end
            end
        end
    elseif t.fsm == FSMCreature then
        local cfgs = t.cfg[t.waypoint].creature
        if cfgs then
            local cb = function(pCreature) table.insert(t.creatures, pCreature) end
            local timer = t.instance:castToWheelRoutineType():NewUniqueRoutineType()
            table.insert(t.timers, timer)
            for i, cfg in ipairs(cfgs) do
                DoItOrDelay(t.instance, function()
                    t.instance:DeployStageCreature(cb, table.unpack(cfg))
                end, cfg.t, timer)
            end
        end
        t.fsm, t.action = FSMAction, 0
    elseif t.fsm == FSMAction then
        if not t.tgtime or t.tgtime < systime() then
            local cfgs = t.cfg[t.waypoint].action
            if not cfgs or t.action >= #cfgs then
                t.fsm, t.tgtime = FSMWait, false
            else
                t.action = t.action + 1
                t.tgtime = systime() + table.back(cfgs[t.action])
                t.DoAction(cfgs[t.action])
            end
        end
    elseif t.fsm == FSMWait then
        if not t.tgtime then
            t.tgtime = systime() + t.cfg[t.waypoint].wait
        end
        if t.tgtime < systime() then
            t.waypoint = t.waypoint + 1
            t.fsm, t.tgtime = FSMWalk, false
        end
    end
end

function t.DoAction(cfg)
    if cfg[1] == ActionBubble then
        t.actor:SendStoryBubble(cfg[2], cfg[3])
    elseif cfg[1] == ActionAnim then
        t.pPlayer:SendStoryAnim(cfg[2])
    elseif cfg[1] == ActionAct then
        t.actor:SendStoryAct(cfg[2])
    end
end

local t4actor = {}
t4actor.events = {
    ObjectHookEvent.OnUnitDead,
}

function t4actor.OnUnitDead()
    t.pQuestLog:Failed()
    t.Player:SendPlayTips(11, 3000)
end

local t4player = {}
t4player.events = {
    ObjectHookEvent.OnPlayerChangeQuestStatus,
    ObjectHookEvent.OnPlayerDelete,
    ObjectHookEvent.OnSendMessage,
}

function t4player.OnPlayerChangeQuestStatus(pQuestLog, type)
    if pQuestLog == t.pQuestLog then
        if type == QuestWhenType.Finish || type == QuestWhenType.Failed ||
           type == QuestWhenType.Cancel then
            t.pPlayer:DetachObjectHookInfo(t4player.handler)
            t.CleanConvoyStatus()
        end
        if type == QuestWhenType.Failed then
            t.Player:SendPlayTips(11, 3000)
        end
    end
end

function t4player.OnPlayerDelete()
    t.CleanConvoyStatus()
end

function t4player.SaveConvoyStatus()
    local pos, o =  t.actor:GetPosition(), t.actor:GetOrientation()
    local status = {t.waypoint, t.actor:GetHPRate(), pos.x, pos.y, pos.z, o}
    t.pPlayer:SaveConvoyStatus(pQuestLog:GetQuestTypeID(), table.concat(status))
end